-- Support for the Extravaganza
--
-- Map support liberally borrowed from GuildMap, by Bru on Blackhand

FishingBuddy.Extravaganza = {};

local NUMMINIPOIS = 10;
local ICONPATH = "Interface\\AddOns\\FishingBuddy\\Icons\\";

-- the actual names don't matter, except to help make sure I've got 'em all
local ExtravaganzaFish = {};
if GetLocale() == "zhCN" then
    ExtravaganzaFish[19807] = "斑点可口鱼";
    ExtravaganzaFish[19806] = "迪森皇后鱼";
    ExtravaganzaFish[19805] = "基佛天使鱼";
    -- makes you wonder what item 19804 is, doesn't it...
    ExtravaganzaFish[19803] = "布隆奈尔蓝斑鱼";
else
    ExtravaganzaFish[19807] = "Speckled Tastyfish";
    ExtravaganzaFish[19806] = "Dezian Queenfish";
    ExtravaganzaFish[19805] = "Keefer's Angelfish";
    -- makes you wonder what item 19804 is, doesn't it...
    ExtravaganzaFish[19803] = "Brownell's Blue Striped Racer";
end
FishingBuddy.Extravaganza.Fish = ExtravaganzaFish;

local UPDATETIME_POI = 0.2;
local UPDATETIME_SCHOOLS = 0.1;
local UPDATETIME_COUNTER = 60.0;
local STVUpdateTimer = 0;
local numCaught = 0;
local tastyfish_id = 19807;
local tastyfish;
local ExtravaganzaIsOver;

-- convert zone coords into minimap coords
local STVInfo = {
    scale = 0.18128603034401,
    xoffset = 0.39145470225916,
    yoffset = 0.79412224886668
};

local ZoomScale = {};
ZoomScale[0] = { xscale = 10448.3, yscale = 7072.7 };
ZoomScale[1] = { xscale = 12160.5, yscale = 8197.8 };
ZoomScale[2] = { xscale = 14703.1, yscale = 9825.0 };
ZoomScale[3] = { xscale = 18568.7, yscale = 12472.2 };
ZoomScale[4] = { xscale = 24390.3, yscale = 15628.5 };
ZoomScale[5] = { xscale = 37012.2, yscale = 25130.6 };

local function GetSTVPosition(x, y)
    if (not x or not y) then
        x, y = GetPlayerMapPosition("player");
    end
    x = (x * STVInfo.scale) + STVInfo.xoffset;
    y = (y * STVInfo.scale) + STVInfo.yoffset;
    return x, y;
end

local function SetTextureCoords(texture, A, B, C, D, E, F)
    local det = A * E - B * D;
    local ULx, ULy, LLx, LLy, URx, URy, LRx, LRy;

    ULx, ULy = (B * F - C * E) / det, (-(A * F) + C * D) / det;
    LLx, LLy = (-B + B * F - C * E) / det, (A - A * F + C * D) / det;
    URx, URy = (E + B * F - C * E) / det, (-D - A * F + C * D) / det;
    LRx, LRy = (E - B + B * F - C * E) / det, (-D + A - (A * F) + C * D) / det;

    texture:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy);
end

local function SetTextureAngle(texture, angrads)
    local c = math.cos(angrads);
    local s = math.sin(angrads);
    SetTextureCoords(texture, c, s, 0, -s, c, 0);
end

local function GetAngleIcon(x, y)
    local angle = asin(x / 57);
    if (x <= 0 and y <= 0) then
        angle = 180 - angle;
    elseif (x <= 0 and y > 0) then
        angle = 360 + angle;
    elseif (x > 0 and y >= 0) then
        angle = angle;
    else
        angle = 180 - angle;
    end
    local fileNumber = math.floor((angle / 10) + 0.5) * 10;
    if (fileNumber == 360) then
        fileNumber = 0;
    end
    return ICONPATH .. "MiniMapArrow" .. fileNumber;
end

local function PlotPOI(index, x, y, rot, scale)
    local poi = getglobal("FishingExtravaganzaMini" .. index);
    if (poi) then
        if (x and y) then
            local tex = getglobal("FishingExtravaganzaMini" .. index .. "Texture");
            local zoom = ZoomScale[Minimap:GetZoom()];
            x = x * zoom.xscale;
            y = y * zoom.yscale;
            local dist = math.sqrt(x * x + y * y);
            if (dist > 56.5) then
                x = x * 57 / dist;
                y = y * 57 / dist;
                if (rot) then
                    tex:SetTexture(ICONPATH .. "MiniMapArrow");
                    SetTextureAngle(tex, math.asin(x / 57));
                else
                    tex:SetTexture(GetAngleIcon(x, y));
                    tex:SetTexCoord(0.0, 1.0, 0.0, 1.0);
                end
            else
                tex:SetTexture("Interface\\Minimap\\ObjectIcons");
                tex:SetTexCoord(0.0, 0.25, 0.25, 0.5);
            end
            poi:SetPoint("CENTER", "MinimapCluster", "TOPLEFT", 107 + x, y - 92);
            if (scale) then
                poi:SetWidth(scale);
                poi:SetHeight(scale);
            end
            poi:Show();
            return true;
        else
            poi:Hide();
        end
    end
    return false;
end

local function CloseEnough(x1, y1, x2, y2)
    local zoom = Minimap:GetZoom();
    local x = (x1 - x2) * ZoomScale[zoom].xscale;
    local y = (y1 - y2) * ZoomScale[zoom].yscale;
    if (sqrt((x * x) + (y * y)) > 56.5) then
        return; -- false
    end
    return true;
end

local function GetDistance(x1, y1, x2, y2)
    local dx = (x1 - x2);
    local dy = (y1 - y2);
    return math.sqrt(dx * dx + dy * dy);
end

local function GetMinimapDistance(x1, y1, x2, y2)
    local zoom = ZoomScale[Minimap:GetZoom()];
    local dx = (x1 - x2) * zoom.xscale;
    local dy = (y1 - y2) * zoom.yscale;
    return math.sqrt(dx * dx + dy * dy);
end

local lx, ly;
local lastten;
local function GetNearestTen(x, y)
    local schools = FishingBuddy.Schools.GetSchools(FishingBuddy.STVZONENAME);
    if (not x or not y) then
        x, y = GetSTVPosition();
    end
    -- if we've moved less than half the distance to the closest hole
    -- don't recalculate
    if (lastten) then
        if (lx == x and ly == y) then
            return lastten;
        end
        if (table.getn(lastten) > 0) then
            local d = GetDistance(x, y, lx, ly);
            if (d < (lastten[1].dist / 2)) then
                return lastten;
            end
        end
    end

    local bydist = {};
    for idx = 1, table.getn(schools) do
        local t = schools[idx];
        if (t.kind == FishingBuddy.SCHOOL_TASTY) then
            local sx, sy = GetSTVPosition(t.x, t.y);
            local d = GetDistance(sx, sy, x, y);
            local info = {};
            info.x = sx;
            info.y = sy;
            info.dist = d;
            tinsert(bydist, info);
        end
    end
    table.sort(bydist, function(a, b) return a.dist < b.dist; end);
    local idx = 1;
    lastten = {};
    for _, info in bydist do
        if (idx < 10) then
            tinsert(lastten, { dist = info.dist, x = info.x, y = info.y });
            idx = idx + 1;
        end
    end
    lx = x;
    ly = y;
    return lastten;
end

local function UpdatePOI()
    local x, y = GetSTVPosition();
    local loc1 = GetNearestTen(x, y);
    for idx = 1, NUMMINIPOIS do
        local t = loc1[idx];
        if (t) then
            PlotPOI(idx, t.x - x, y - t.y);
        else
            PlotPOI(idx);
        end
    end
end

local function HidePOI()
    local poi = getglobal("FishingExtravaganzaMini1");
    if (poi:IsVisible()) then
        for idx = 1, NUMMINIPOIS do
            PlotPOI(idx);
        end
    end
end

local POIUpdateTimer = UPDATETIME_POI;
FishingBuddy.Extravaganza.POI_OnUpdate = function(elapsed)
    POIUpdateTimer = POIUpdateTimer - elapsed;
    if (POIUpdateTimer < 0) then
        FishingExtravaganzaPOIUpdate:Hide();
        UpdatePOI();
        POIUpdateTimer = UPDATETIME_POI;
    end
end

local function DoUpdatePOI()
    POIUpdateTimer = UPDATETIME_POI;
    FishingExtravaganzaPOIUpdate:Show();
end

-- let an external entity forcibly mark a school
FishingBuddy.Extravaganza.MarkSchool = function()
    local zone, subzone = FishingBuddy.GetZoneInfo();
    if (zone == FishingBuddy.STVZONENAME) then
        FishingBuddy.Schools.AddFishingSchool(FishingBuddy.SCHOOL_TASTY, tastyfish_id, zone);
    end
end

local function ExtravaganzaHijackCheck()
    if (FishingBuddy.NormalHijackCheck()) then
        -- also check to make sure we're over a pool
        return FishingBuddy.Schools.IsFishingHole(FishingBuddy.GetTooltipText());
    end
end

-- Sunday, 2pm
local STVDay = "0";
local STVStartHour = 14;

-- Should we display the extravaganza message?
local function IsTime(activate)
    local showit = false;
    if (FishingBuddy.IsLoaded()) then
        if (FishingBuddy.GetSetting("STVTimer") == 1) then
            local mhour = date("%H");
            local hour, minute = GetGameTime();
            local day = date("%w");
            local off = FishingBuddy.GetClockOffset();
            if (off ~= 0) then
                local lhour = date("%H");
                lhour = lhour + off;
                if (lhour >= 24) then
                    day = day + 1;
                elseif (lhour < 0) then
                    day = day - 1;
                end
            end
            -- Is it Sunday?
            if (day == STVDay and
                    (hour >= (STVStartHour - 2) and hour < (STVStartHour + 2))) then
                showit = true;
            end
        end
    end
    if (showit) then
        if (activate) then
            FishingExtravaganzaFrame:Show();
        end
        if (FishingBuddy.GetSetting("STVPoolsOnly") == 1) then
            local zone, _ = FishingBuddy.GetZoneInfo();
            if (zone == FishingBuddy.STVZONENAME) then
                FishingBuddy.SetHijackCheck(ExtravaganzaHijackCheck);
            else
                FishingBuddy.SetHijackCheck();
            end
        end
    else
        if (FishingExtravaganzaFrame:IsVisible() or
                FishingExtravaganzaMini1:IsVisible()) then
            FishingExtravaganzaFrame:Hide();
            HidePOI();
        end
        FishingBuddy.SetHijackCheck();
    end
    return showit;
end
FishingBuddy.Extravaganza.IsTime = IsTime;

-- Check for mouse down event for dragging frame.
FishingBuddy.Extravaganza.OnDragStart = function(arg1)
    if (arg1 == "LeftButton") then
        FishingExtravaganzaFrame:StartMoving();
        FishingExtravaganzaFrame.isMoving = true;
    end
end

-- Check for drag stop event to stop dragging.
FishingBuddy.Extravaganza.OnDragStop = function(arg1)
    if (arg1 == "LeftButton") then
        FishingExtravaganzaFrame:StopMovingOrSizing();
        FishingExtravaganzaFrame.isMoving = false;
    end
end

-- Handle watching the loot
FishingBuddy.Extravaganza.OnLoad = function()
    this:RegisterEvent("PLAYER_LOGIN");
    this:RegisterEvent("ZONE_CHANGED_NEW_AREA");
    this:RegisterEvent("VARIABLES_LOADED");

    this:RegisterForDrag("LeftButton");
    this:Hide();
end

FishingBuddy.Extravaganza.OnShow = function()
    -- check each of the bags on the player
    numCaught = 0;
    for bag = 0, NUM_BAG_FRAMES do
        -- get the number of slots in the bag (0 if no bag)
        numSlots = GetContainerNumSlots(bag);
        if (numSlots > 0) then
            -- check each slot in the bag
            for slot = 1, numSlots do
                local link = GetContainerItemLink(bag, slot);
                if (link) then
                    local _, id, _ = FishingBuddy.SplitFishLink(link);
                    if (id and id == tastyfish_id) then
                        local _, c, _, _, _ = GetContainerItemInfo(bag, slot);
                        numCaught = numCaught + c;
                    end
                end
            end
        end
    end
end

FishingBuddy.Extravaganza.OnEvent = function()
    local zone, subzone = FishingBuddy.GetZoneInfo();
    if (event == "LOOT_OPENED") then
        if (IsFishingLoot()) then
            for index = 1, GetNumLootItems(), 1 do
                if (LootSlotIsItem(index)) then
                    local fishlink = GetLootSlotLink(index);
                    local _, id, name = FishingBuddy.SplitFishLink(fishlink);
                    if (ExtravaganzaFish[id]) then
                        FishingBuddy.Schools.AddFishingSchool(FishingBuddy.SCHOOL_TASTY, id, zone);
                        STVUpdateTimer = 0;
                        if (id == tastyfish_id) then
                            numCaught = numCaught + 1;
                            -- make sure we've got the name of the fish for display
                            tastyfish = name;
                        end
                    end
                end
            end
        end
    elseif (event == "CHAT_MSG_YELL") then
        -- Riggle Bassbait yells: We have a winner! (.*) is the Master Angler!
        local e, s, n = string.find(arg1, FishingBuddy.RIGGLE_BASSBAIT);
        if (e) then
            ExtravaganzaIsOver = true;
        end
    elseif (event == "MINIMAP_UPDATE_ZOOM") then
        if (zone == FishingBuddy.STVZONENAME) then
            if (FishingBuddy.GetSetting("STVTimer") == 1) then
                DoUpdatePOI();
            end
        end
    elseif (event == "ZONE_CHANGED_NEW_AREA" or event == "PLAYER_LOGIN") then
        if (zone == FishingBuddy.STVZONENAME and IsTime()) then
            this:RegisterEvent("CHAT_MSG_YELL");
            this:RegisterEvent("LOOT_OPENED");
            this:RegisterEvent("MINIMAP_UPDATE_ZOOM");
        else
            this:UnregisterEvent("CHAT_MSG_YELL");
            this:UnregisterEvent("LOOT_OPENED");
            this:UnregisterEvent("MINIMAP_UPDATE_ZOOM");
        end
    elseif (event == "VARIABLES_LOADED") then
        local _, _, _, _, _, n = FishingBuddy.GetFishie(tastyfish_id);
        if (n) then
            tastyfish = n;
        else
            tastyfish = FishingBuddy.FISH;
        end
        IsTime(true);
        this:UnregisterEvent("VARIABLES_LOADED");
    end
end

FishingBuddy.Extravaganza.OnUpdate = function(elapsed)
    if (IsTime()) then
        if (not FishingExtravaganzaFrame:IsVisible()) then
            FishingExtravaganzaFrame:Show();
        end
        STVUpdateTimer = STVUpdateTimer - elapsed;
        if (STVUpdateTimer < 0) then
            local hour, minute = GetGameTime();
            local minleft;
            local checkhour = STVStartHour;
            local line;
            local contestNow = false;
            if (hour >= STVStartHour) then
                if (ExtravaganzaIsOver) then
                    line = FishingBuddy.FATLADYSINGS;
                else
                    line = FishingBuddy.TIMELEFT;
                end
                line = line .. FishingBuddy.DASH .. " |c";
                if (numCaught < 20) then
                    line = line .. FishingBuddy.Colors.RED;
                elseif (numCaught < 39) then
                    line = line .. FishingBuddy.Colors.YELLOW;
                else
                    line = line .. FishingBuddy.Colors.GREEN;
                end
                line = line .. FishingBuddy.FISHCAUGHT .. "|r";
                checkhour = checkhour + 2;
                contestNow = true;
            else
                line = FishingBuddy.TIMETOGO;
            end
            minleft = (checkhour - hour) * 60 - minute;
            if (minleft > 0) then
                FishingExtravaganzaFrameButtonText:SetTextColor(0.1, 1.0, 0.1);
                if (minleft < 10) then
                    FishingExtravaganzaFrameButtonText:SetTextColor(1.0, 0.1, 0.1);
                end
                line = string.format(line, minleft / 60, math.mod(minleft, 60),
                    numCaught, tastyfish);
                FishingExtravaganzaFrameButtonText:SetText(line);
                local width = FishingExtravaganzaFrameButtonText:GetWidth();
                FishingExtravaganzaFrame:SetWidth(width + 16);
                local zone, _ = FishingBuddy.GetZoneInfo();
                if (zone == FishingBuddy.STVZONENAME) then
                    DoUpdatePOI();
                    STVUpdateTimer = UPDATETIME_SCHOOLS;
                else
                    STVUpdateTimer = UPDATETIME_COUNTER;
                end
            end
        end
    else
        FishingExtravaganzaFrame:Hide();
        HidePOI();
    end
end

local function GetObjectCoords(poi, index, numcolumns, texturewidth)
    local width = poi:GetWidth();
    local xCoord1, xCoord2, yCoord1, yCoord2;
    local coordIncrement = width / texturewidth;
    xCoord1 = mod(index, numcolumns) * coordIncrement;
    xCoord2 = xCoord1 + coordIncrement;
    yCoord1 = floor(index / numcolumns) * coordIncrement;
    yCoord2 = yCoord1 + coordIncrement;
    return xCoord1, xCoord2, yCoord1, yCoord2;
end

local start = 0;
-- debugging routines
FishingBuddy.Extravaganza.Debug = function(day, hour, zone)
    STVDay = day;
    STVStartHour = hour;
    if (zone) then
        FishingBuddy.STVZONENAME = zone;
    end
    IsTime(true);
end

-- eventually, display what fish you caught here
FishingBuddy.Extravaganza.MiniMap_OnEnter = function()
end

-- test the extravaganze school marking functions
-- need to expand this for 1.9 if we can tell automatically
FishingBuddy.Commands["mark"] = {};
FishingBuddy.Commands["mark"].func =
    function(what)
        if (what == "reset") then
            FishingBuddy_Info["FishSchools"] = nil;
        elseif (what == "debug") then
            local hour, _ = GetGameTime();
            local day = date("%w");
            FishingBuddy.Extravaganza.Debug(day, hour);
        elseif (what == "test") then
            FishingBuddy.Debug("mark test");
            local coord = 0.005;
            local rot = 1;
            PlotPOI(1, 0, coord, rot, 16);
            PlotPOI(2, coord, coord, rot, 16);
            PlotPOI(3, coord, 0, rot, 16);
            PlotPOI(4, -coord, 0, rot, 16);
            PlotPOI(5, -coord, -coord, rot, 16);
            PlotPOI(6, 0, -coord, rot, 16);
        else
            FishingBuddy.Extravaganza.MarkSchool();
        end
        return true;
    end;
